/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openFuyao is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package initialize

import (
	"path/filepath"
	"testing"

	"github.com/spf13/afero"
	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"gopkg.openfuyao.cn/bkeadm/pkg/executor/k8s"
	"gopkg.openfuyao.cn/bkeadm/pkg/global"
	"gopkg.openfuyao.cn/bkeadm/utils"
)

// 模拟 SetPatchConfig：记录调用参数
type patchRecorder struct {
	calls []patchCall
}

type patchCall struct {
	version, path, key string
}

func (pr *patchRecorder) setPatch(version, path, key string) error {
	pr.calls = append(pr.calls, patchCall{
		version: version,
		path:    path,
		key:     key})
	return nil
}

func TestOptions_offlineGenerateDeployCM(t *testing.T) {
	// Setup
	fs := afero.NewMemMapFs()
	patchesDir := filepath.Join(global.Workspace, utils.PatchDataDirectory)
	afero.WriteFile(fs, filepath.Join(patchesDir, "v1.2.3.yaml"), []byte("dummy"), utils.DefaultFilePermission)

	recorder := &patchRecorder{}
	op := &Options{
		OFVersion:        "v1.2.3",
		FS:               fs,
		SetPatchConfigFn: recorder.setPatch,
	}

	// Execute
	err := op.offlineGenerateDeployCM()
	assert.NoError(t, err)

	// Assert
	assert.Len(t, recorder.calls, 1)
	call := recorder.calls[0]
	assert.Equal(t, "v1.2.3", call.version)
	assert.Equal(t, filepath.Join(patchesDir, "v1.2.3.yaml"), call.path)
	assert.Equal(t, utils.PatchValuePrefix+"v1.2.3", call.key)
}

func TestOptions_offlineGenerateDeployCM_NotFound(t *testing.T) {
	fs := afero.NewMemMapFs()
	patchesDir := filepath.Join(global.Workspace, utils.PatchDataDirectory)
	afero.WriteFile(fs, filepath.Join(patchesDir, "v1.2.2.yaml"), []byte("dummy"), utils.DefaultFilePermission)

	recorder := &patchRecorder{}
	op := &Options{
		OFVersion:        "v1.2.3", // not exist
		FS:               fs,
		SetPatchConfigFn: recorder.setPatch,
	}

	err := op.offlineGenerateDeployCM()
	assert.Error(t, err)

	// Should not call setPatch
	assert.Empty(t, recorder.calls)
}

func TestOptions_onlineGenerateDeployCM(t *testing.T) {
	fs := afero.NewMemMapFs()
	recorder := &patchRecorder{}

	downloadCalls := make([]string, 0)
	mockDownload := func(url, dest string) error {
		downloadCalls = append(downloadCalls, url)
		// Simulate writing file
		if filepath.Base(dest) == "index.yaml" {
			content := `
- openFuyaoVersion: v1.2.3
  filePath: ./patches/v1.2.3.yaml
`
			return afero.WriteFile(fs, dest, []byte(content), utils.DefaultFilePermission)
		} else if filepath.Base(dest) == "v1.2.3.yaml" {
			return afero.WriteFile(fs, dest, []byte("patch content"), utils.DefaultFilePermission)
		}
		return nil
	}

	op := &Options{
		OFVersion:        "v1.2.3",
		ClusterAPI:       "my-cluster",
		FS:               fs,
		DownloadFunc:     mockDownload,
		SetPatchConfigFn: recorder.setPatch,
	}

	err := op.onlineGenerateDeployCM()
	assert.NoError(t, err)

	// Should download index.yaml and v1.2.3.yaml
	assert.Contains(t, downloadCalls, utils.DefaultPatchDownURL+"my-cluster/index.yaml")
	assert.Contains(t, downloadCalls, utils.DefaultPatchDownURL+"my-cluster/patches/v1.2.3.yaml")

	// Should call setPatch
	assert.Len(t, recorder.calls, 1)
	call := recorder.calls[0]
	assert.Equal(t, "v1.2.3", call.version)
	assert.Equal(t, filepath.Join(global.Workspace, utils.PatchDataDirectory, "patches", "v1.2.3.yaml"), call.path)
	assert.Equal(t, utils.PatchValuePrefix+"v1.2.3", call.key)
}

func TestOptions_onlineGenerateDeployCM_IndexFail(t *testing.T) {
	fs := afero.NewMemMapFs()
	recorder := &patchRecorder{}

	mockDownload := func(url, dest string) error {
		return assert.AnError // simulate download fail
	}

	op := &Options{
		OFVersion:        "v1.2.3",
		ClusterAPI:       "my-cluster",
		FS:               fs,
		DownloadFunc:     mockDownload,
		SetPatchConfigFn: recorder.setPatch,
	}

	err := op.onlineGenerateDeployCM()
	assert.Error(t, err)

	// Should not call setPatch
	assert.Empty(t, recorder.calls)
}

func TestOptions_generateDeployCM_Offline(t *testing.T) {
	oc.Repo = "" // trigger offline
	defer func() { oc.Repo = "test" }()

	fs := afero.NewMemMapFs()
	patchesDir := filepath.Join(global.Workspace, utils.PatchDataDirectory)
	afero.WriteFile(fs, filepath.Join(patchesDir, "v1.2.3.yaml"), []byte("dummy"), utils.DefaultFilePermission)

	recorder := &patchRecorder{}
	op := &Options{
		OFVersion:        "v1.2.3",
		FS:               fs,
		SetPatchConfigFn: recorder.setPatch,
	}

	err := op.generateDeployCM()
	assert.NoError(t, err)

	assert.Len(t, recorder.calls, 1)
}

func TestOptions_generateDeployCM_Online(t *testing.T) {
	oc.Repo = "http://example.com" // trigger online
	defer func() { oc.Repo = "" }()

	fs := afero.NewMemMapFs()
	recorder := &patchRecorder{}

	mockDownload := func(url, dest string) error {
		if filepath.Base(dest) == "index.yaml" {
			content := `
- openFuyaoVersion: v1.2.3
  filePath: ./patches/v1.2.3.yaml
`
			return afero.WriteFile(fs, dest, []byte(content), utils.DefaultFilePermission)
		}
		return afero.WriteFile(fs, dest, []byte("content"), utils.DefaultFilePermission)
	}

	op := &Options{
		OFVersion:        "v1.2.3",
		ClusterAPI:       "test",
		FS:               fs,
		DownloadFunc:     mockDownload,
		SetPatchConfigFn: recorder.setPatch,
	}

	err := op.generateDeployCM()
	assert.NoError(t, err)

	assert.Len(t, recorder.calls, 1)
}

func TestGetClusterAPIVersion_Success(t *testing.T) {
	yamlContent := `
repos:
- subImages:
  - images:
    - name: bke-manifests
      tag: ["v1.2.3"]
    - name: cluster-api-provider-bke
      tag: ["v4.5.6"]
`
	cm := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "cm.v2.0",
			Namespace: "openfuyao-patch",
		},
		Data: map[string]string{
			"v2.0": yamlContent,
		},
	}

	op := &Options{
		OFVersion: "v2.0",
		K8sClient: &k8s.MockK8sClient{ConfigMap: cm},
	}

	manifestsVer, providerVer := op.getClusterAPIVersion("v2.0", "default")

	if manifestsVer != "v1.2.3" {
		t.Errorf("expected manifests v1.2.3, got %s", manifestsVer)
	}
	if providerVer != "v4.5.6" {
		t.Errorf("expected provider v4.5.6, got %s", providerVer)
	}
}

func TestGetClusterAPIVersion_DataKeyMissing(t *testing.T) {
	cm := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{Name: "cm.v2.0"},
		Data:       map[string]string{"other-version": "xxx"}, // 缺少 "v2.0" key
	}

	op := &Options{
		OFVersion: "v2.0",
		K8sClient: &k8s.MockK8sClient{ConfigMap: cm},
	}

	manifestsVer, providerVer := op.getClusterAPIVersion("v2.0", "fallback")

	if manifestsVer != "fallback" || providerVer != "fallback" {
		t.Errorf("expected fallback due to missing key, got %s / %s", manifestsVer, providerVer)
	}
}

func TestGetClusterAPIVersion_YAMLParseError(t *testing.T) {
	cm := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{Name: "cm.v2.0"},
		Data: map[string]string{
			"v2.0": "invalid: yaml: [",
		},
	}

	op := &Options{
		OFVersion: "v2.0",
		K8sClient: &k8s.MockK8sClient{ConfigMap: cm},
	}

	manifestsVer, providerVer := op.getClusterAPIVersion("v2.0", "fallback")

	if manifestsVer != "fallback" || providerVer != "fallback" {
		t.Errorf("expected fallback due to YAML error, got %s / %s", manifestsVer, providerVer)
	}
}
